package com.hewep.demo.nio;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class Server implements Runnable {

	//主线程中的写通道池，用来存放从读取之后可以进行写操作的通道。
	private static List<SelectionKey> wpool = new LinkedList<SelectionKey>();
	private Notify notify = Notify.getInstance();
	private ServerSocketChannel ssChannel;
	private static Selector selector;
	private int port;
	
	public Server(int port) throws Exception{
		
		this.port = port;
		
		//创建了一个读线程和一个写线程
		new Thread(new Reader()).start();
		new Thread(new Writer()).start();
		
		selector = Selector.open();
		ssChannel = ServerSocketChannel.open();
		ssChannel.configureBlocking(false);
		ServerSocket ss = ssChannel.socket();
		InetSocketAddress address = new InetSocketAddress(port);
		ss.bind(address);
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("Server start..");
		System.out.println("Server listening on port: " + port);
		while (true){
			try{
				int num = 0;
				// 阻塞着等待着感兴趣的通道完成，但是如果此时一个读通道已经完成了，可以进行写通道的任务时，根据processWriteRequest唤醒它，唤醒后num=0;
				num = selector.select();
				if (num > 0) {
					Set<SelectionKey> set = selector.selectedKeys();
					Iterator<SelectionKey> it = set.iterator();
					while (it.hasNext()){
						SelectionKey sk = it.next();
						
						//在已选择集中删除，如果不删除下次选择时它还是会存在的。
						it.remove();
						
						if (sk.isAcceptable()) {
							ServerSocketChannel ssc = (ServerSocketChannel) sk.channel();
							
							//这步没有用到，可以用来做已连接数的检测
							notify.fireOnAccept();
							
							SocketChannel sc = ssc.accept();
							sc.configureBlocking(false);
							Request request = new Request(sc);
							
							// 注册读操作,以进行下一步的读操作, 这里将request作为一个附加对象加入进去。
							sc.register(selector, SelectionKey.OP_READ, request);
						}
						
						//可读的通道就绪了
						else if (sk.isReadable()) {
							Reader.processRequest(sk);
							sk.cancel();
						}
						//可写的通道就绪了
						else if (sk.isWritable()) {
							Writer.processRequest(sk);
							sk.cancel();
						}
					}
				}
				else{
					addRegister();  // 在Selector中注册新的写通道
				}
			}catch (Exception e) {
				// TODO: handle exception
				notify.fireError("Error occured in Server: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}
	
	
	//在读线程中将会调用这个方法，告诉主线程读通道的事件已经完成，现在可以进行写处理
	public static void processWriteRequest(SelectionKey key){
		synchronized (wpool) {
            wpool.add(wpool.size(), key);
            wpool.notifyAll();
        }
		selector.wakeup();   // 解除selector的阻塞状态，以便注册新的写通道，否则他会一直在select哪里阻塞着等待
	}

	//这个方法是将主线程中通道池里的准备好写的通道，注册到selector中。
	public void addRegister(){
		synchronized (wpool) {
		
			while (!wpool.isEmpty()) {
				SelectionKey key = wpool.remove(0);
				SocketChannel sc = (SocketChannel) key.channel();
				try{
					//将这个通道中的从客户端发来的包装好的request当做key中的附带物想selector注册
					sc.register(selector, SelectionKey.OP_WRITE, key.attachment());
				}catch (Exception e) {
					// TODO: handle exception
					try{
						sc.finishConnect();
						sc.socket().close();
						sc.close();
					}catch (Exception ex) {
						// TODO: handle exception
						ex.printStackTrace();
					}
					e.printStackTrace();		
				}
			}
			
		}
	}
	
}
